package com.sec.etech.bpm.cust.controller;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.map.MapBuilder;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.bpmn.model.Process;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.Model;
import org.activiti.engine.repository.ProcessDefinition;
import org.apache.commons.lang3.StringUtils;
import org.openbpm.base.api.aop.annotion.CatchErr;
import org.openbpm.base.api.exception.BusinessMessage;
import org.openbpm.base.api.response.impl.ResultMsg;
import org.openbpm.base.core.id.IdUtil;
import org.openbpm.bpm.api.constant.NodeType;
import org.openbpm.bpm.api.engine.action.cmd.FlowRequestParam;
import org.openbpm.bpm.api.model.nodedef.BpmNodeDef;
import org.openbpm.bpm.api.service.BpmProcessDefService;
import org.openbpm.bpm.core.dao.BpmDefinitionDao;
import org.openbpm.bpm.core.manager.BpmDefinitionManager;
import org.openbpm.bpm.core.manager.BpmInstanceManager;
import org.openbpm.bpm.core.model.BpmDefinition;
import org.openbpm.bpm.core.model.BpmInstance;
import org.openbpm.bpm.rest.controller.BpmDefinitionController;
import org.openbpm.bpm.rest.vo.UserTaskNodeVO;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.*;

@RestController
@RequestMapping({"/etech/definition"})
public class EtechBpmDefinitionController extends BpmDefinitionController {

    @Resource
    BpmDefinitionManager bpmDefinitionManager;

    @Autowired
    BpmProcessDefService bpmProcessDefService;

    @Autowired
    BpmInstanceManager bpmInstanceManager;

    @Resource
    RepositoryService repositoryService;

    @Resource
    BpmDefinitionDao bpmDefinitionDao;

    @RequestMapping({"duplicate"})
    @CatchErr(write2response = true, value = "复制流程定义失败")
    public ResultMsg<String> duplicate(@RequestBody JSONObject params) throws Exception {
        //{"originKey":"flow_001","newTypeId":"20000008980004","newName":"flow_001_duplicate","newKey":"flow_001_duplicate","newDesc":"flow_001_duplciate"}
        String newKey = params.getString("newKey");
        String newName = params.getString("newName");
        String newDesc = params.getString("newDesc");
        String originKey = params.getString("originKey");
        BpmDefinition tempDef = this.bpmDefinitionManager.getByKey(newKey);
        if(tempDef!=null){
            throw new BusinessMessage("流程定义"+newKey+"已存在");
        }

        BpmDefinition bpmDefinitionOrigin = this.bpmDefinitionManager.getByKey(originKey);

        BpmnModel bpmnModel = this.repositoryService.getBpmnModel(bpmDefinitionOrigin.getActDefId());
        Process mainProcess = bpmnModel.getMainProcess();
        if (mainProcess == null && bpmnModel.getProcesses() != null && bpmnModel.getProcesses().size() == 1) {
            mainProcess = bpmnModel.getProcesses().get(0);
        }

        mainProcess.setId(newKey);
        mainProcess.setName(newName);
        String bpmxXml = new String((new BpmnXMLConverter()).convertToXML(bpmnModel, StandardCharsets.UTF_8.displayName()), StandardCharsets.UTF_8);
        Deployment deployment = this.repositoryService.createDeployment().name(newKey).addString(StringUtils.join((Object[])new String[] { newKey, ".bpmn20.xml" }), bpmxXml).deploy();
        ProcessDefinition processDefinition = (ProcessDefinition)this.repositoryService.createProcessDefinitionQuery().deploymentId(deployment.getId()).singleResult();
        Model model = saveModel(newKey, newName, newDesc, bpmDefinitionOrigin, deployment);

        BpmDefinition bpmDefinition = new BpmDefinition();
        BeanUtils.copyProperties(bpmDefinitionOrigin, bpmDefinition);
        bpmDefinition.setId(IdUtil.getSuid());
        bpmDefinition.setKey(newKey);
        bpmDefinition.setName(newName);
        bpmDefinition.setDesc(newDesc);
        bpmDefinition.setTypeId(params.getString("newTypeId"));
        bpmDefinition.setStatus(bpmDefinitionOrigin.getStatus());
        bpmDefinition.setActDefId(processDefinition.getId());
        bpmDefinition.setActModelId(model.getId());
        bpmDefinition.setActDeployId(deployment.getId());
        bpmDefinition.setVersion(1);
        bpmDefinition.setIsMain("Y");
        JSONObject defSetting = JSON.parseObject(bpmDefinitionOrigin.getDefSetting());
        defSetting.put("bpmDefinition", bpmDefinition);
        bpmDefinition.setDefSetting(defSetting.toJSONString());
        this.bpmDefinitionDao.create(bpmDefinition);

        return getSuccessResult(bpmDefinition.getActModelId(), "创建成功");
    }

    private Model saveModel(String newKey, String newName, String newDesc, BpmDefinition originBpmDefinition, Deployment deployment) {
        Model model = this.repositoryService.newModel();
        model.setKey(newKey);
        model.setName(newName);
        model.setDeploymentId(deployment.getId());
        model.setMetaInfo(JSON.toJSONString(
                MapBuilder.create(new LinkedHashMap<>())
                        .put("key", newKey)
                        .put("name", newName)
                        .put("description", newDesc)
                        .put("revision", "1")
                        .build()));
        this.repositoryService.saveModel(model);
        this.repositoryService.addModelEditorSource(model.getId(), this.repositoryService.getModelEditorSource(originBpmDefinition.getActModelId()));
        this.repositoryService.addModelEditorSourceExtra(model.getId(), this.repositoryService.getModelEditorSourceExtra(originBpmDefinition.getActModelId()));
        return model;
    }

    @PostMapping(value = {"/getAllUserTaskNode"}, produces = {"application/json;charset=UTF-8"}, consumes = {"application/json;charset=UTF-8"})
    public ResultMsg<List<UserTaskNodeVO>> getAllUserTaskNode(@RequestBody FlowRequestParam flowRequestParam) {
        BpmNodeDef bpmNodeDef;
        if (StringUtils.isNotEmpty(flowRequestParam.getDefId())) {
            bpmNodeDef = this.bpmProcessDefService.getStartEvent(flowRequestParam.getDefId());
        } else if (StringUtils.isNotEmpty(flowRequestParam.getDefKey())) {
            BpmDefinition bpmDefinition = this.bpmDefinitionManager.getByKey(flowRequestParam.getDefKey());
            if (bpmDefinition == null)
                return ResultMsg.ERROR(String.format("流程定义(%s)不存在", new Object[]{flowRequestParam.getDefKey()}));
            bpmNodeDef = this.bpmProcessDefService.getStartEvent(bpmDefinition.getId());
        } else if (StringUtils.isNotEmpty(flowRequestParam.getInstanceId())) {
            BpmInstance bpmInstance = (BpmInstance) this.bpmInstanceManager.get(flowRequestParam.getInstanceId());
            if (bpmInstance == null)
                return ResultMsg.ERROR(String.format("流程实例不存在(%s)", new Object[]{flowRequestParam.getInstanceId()}));
            bpmNodeDef = this.bpmProcessDefService.getStartEvent(bpmInstance.getDefId());
        } else {
            return ResultMsg.ERROR("参数为空, 请至少传递一个参数(defId、defKey、instanceId)");
        }
        if (bpmNodeDef == null)
            return ResultMsg.ERROR("起始节点不存在");
        List<UserTaskNodeVO> userTaskNodeVOS = new ArrayList<>();
        LinkedList<BpmNodeDef> bpmNodeDefLinkedList = new LinkedList<>(bpmNodeDef.getOutcomeNodes());
        // fix 增加循环检查
        Set<String> nodeIdSet = new HashSet<>();
        while (!bpmNodeDefLinkedList.isEmpty()) {
            BpmNodeDef bpmNodeDefItem = bpmNodeDefLinkedList.remove();
            if(nodeIdSet.contains(bpmNodeDefItem.getNodeId())){
                // 跳过已加入的节点，避免流程中存在循环
                continue;
            }
            if (CollectionUtil.isNotEmpty(bpmNodeDefItem.getOutcomeNodes()))
                bpmNodeDefLinkedList.addAll(bpmNodeDefItem.getOutcomeNodes());
            if (NodeType.USERTASK.equals(bpmNodeDefItem.getType())) {
                UserTaskNodeVO userTaskNodeVO = new UserTaskNodeVO();
                userTaskNodeVO.setNodeId(bpmNodeDefItem.getNodeId());
                userTaskNodeVO.setNodeName(bpmNodeDefItem.getName());
                userTaskNodeVOS.add(userTaskNodeVO);
                nodeIdSet.add(userTaskNodeVO.getNodeId());
            }
        }
        return ResultMsg.SUCCESS(userTaskNodeVOS);
    }

    @RequestMapping({"save"})
    @CatchErr(write2response = true, value = "保存流程定义失败")
    public ResultMsg<String> save(@RequestBody BpmDefinition bpmDefinition) throws Exception {
        String id = bpmDefinition.getId();
        if(StringUtils.isEmpty(id)){
            this.bpmDefinitionManager.create(bpmDefinition);
            return getSuccessResult(bpmDefinition.getActModelId(), "创建成功");
        }else{
            BpmDefinition currentDef = bpmDefinitionManager.get(id);
            boolean changed = false;
            if(StringUtils.isNotEmpty(bpmDefinition.getTypeId()) && !bpmDefinition.getTypeId().equals(currentDef.getTypeId())){
                currentDef.setTypeId(bpmDefinition.getTypeId());
                changed = true;
            }
            // key 不允许改
            if(StringUtils.isNotEmpty(bpmDefinition.getName()) && !bpmDefinition.getName().equals(currentDef.getName())){
                currentDef.setName(bpmDefinition.getName());
                changed = true;
            }
            if(StringUtils.isNotEmpty(bpmDefinition.getDesc()) && !bpmDefinition.getDesc().equals(currentDef.getDesc())){
                currentDef.setDesc(bpmDefinition.getDesc());
                changed = true;
            }
            if(changed){
                bpmDefinitionManager.update(currentDef);
                return getSuccessResult(bpmDefinition.getActModelId(), "更新成功");
            }

            return getSuccessResult(bpmDefinition.getActModelId(), "无更新");
        }
    }
}
